// Copyright (c) 2010, Razvan Petru
// All rights reserved.

// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:

// * Redistributions of source code must retain the above copyright notice, this
//   list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice, this
//   list of conditions and the following disclaimer in the documentation and/or other
//   materials provided with the distribution.
// * The name of the contributors may not be used to endorse or promote products
//   derived from this software without specific prior written permission.

// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.

#include "qslogdest.h"
#include "qsdebugoutput.h"
#include <QFile>
#include <QTextStream>
#include <QString>
#include <QSharedPointer>
#include <QFileInfo>
#include <QDir>
#include <QDateTime>

namespace QsLogging
{

	//! file message sink
	class FileDestination : public Destination
	{
	public:
		FileDestination(const QString& filePath, const qint32 maxFileSize = 100 * 1024 * 1024, const qint32 maxFileNumber = 10000);
		virtual void write(const QString& message, Level level);
	private:
		void openFile();
		void createDir(QString& dir);
	private:
		QSharedPointer<QFile> mFile;
		QSharedPointer<QTextStream> mOutputStream;
		qint32 maxFileSize;
		qint32 maxFileNumber;
		QString filePath;

		//current max file number
		qint32 seq;
	};


	FileDestination::FileDestination(const QString& filePath, const qint32 maxFileSize , const qint32 maxFileNumber)
		: maxFileSize(maxFileSize), maxFileNumber(maxFileNumber), filePath(filePath), seq(0)
	{
		QFileInfo fileInfo(filePath);
		QDir parentDir = fileInfo.dir();
		createDir(parentDir.path());
		openFile();
	}
	void FileDestination::createDir(QString& path)
	{
		QFileInfo fileInfo(path);
		if(fileInfo.exists())
		{
			return;
		}
		QDir parentDir = fileInfo.dir();
		if (!parentDir.exists())
		{
			createDir(parentDir.path());	
			
		}
		parentDir.mkdir(fileInfo.fileName());
	}
	void FileDestination::openFile()
	{
		mFile = QSharedPointer<QFile>(new QFile);
		mFile->setFileName(filePath);
		if(mFile->open(QFile::Append | QFile::WriteOnly | QFile::Text) == false)
		{
			throw std::exception((QString("Failed to open log file ") + filePath + mFile->errorString()).toStdString().c_str());
		}
		mOutputStream = QSharedPointer<QTextStream>(new QTextStream);
		mOutputStream->setDevice(mFile.data());

	}
	void FileDestination::write(const QString& message, Level)
	{
		*mOutputStream << message << endl;
		mOutputStream->flush();
		if(mFile->size() >= maxFileSize)
		{
			mOutputStream.clear();
			mFile.clear();
			//rename
			int earlySeq = -1;
			QDateTime mostTime;
			int nextSeq = seq ;
			int count = 0;
			while(QFile::exists(filePath + "." + QString::number(nextSeq )))
			{
				QFileInfo fi(filePath + "." + QString::number(nextSeq ));
				if(earlySeq == -1 || fi.lastModified() < mostTime)
				{
					earlySeq = nextSeq ;
					mostTime =	fi.lastModified();
				}
				nextSeq = (nextSeq  + 1 + maxFileNumber) % maxFileNumber;
				count++;
					if(count >= maxFileNumber)
				{
					break;
				}
			}

			if(count >= maxFileNumber )
			{
				int tmp = earlySeq ;
				while(QFile::remove(filePath + "." + QString::number(tmp)) == false)
				{
					tmp  =  (tmp + 1 + maxFileNumber)  % maxFileNumber;
					if(tmp  == earlySeq)
					{
						break;//continue
						//throw std::exception("the number of log file reached max log file nubmer, all logs may be locked.");
					}
				}
				seq = tmp;
				
			}
			else
			{
				seq = nextSeq;
			}
			count = 0;
			while( QFile::rename(filePath, filePath + "." + QString::number(seq)) == false )
			{		
				seq = (++seq) % maxFileNumber;
				count ++;
				if(count > maxFileNumber)
				{
					break;
					//ignore error.
					//throw std::exception("the number of log file reached max log file nubmer");
				}
			}
			
			seq = (++seq) % maxFileNumber;
			openFile();
		}
	}

	//! debugger sink
	class DebugOutputDestination : public Destination
	{
	public:
		virtual void write(const QString& message, Level level);
	};

	void DebugOutputDestination::write(const QString& message, Level)
	{
		QsDebugOutput::output(message);
	}


	class ConsoleDestination : public Destination
	{
	private:
		QTextStream outputstream;
	public:
		explicit ConsoleDestination(void):outputstream(stdout){}
		virtual void write(const QString& message, Level level);
	};

	void ConsoleDestination::write(const QString& message, Level)
	{
		outputstream << message << endl;
		outputstream.flush();
	}

	DestinationPtr DestinationFactory::MakeFileDestination(const QString& filePath, const qint32 maxFileSize , const qint32 maxFileNumber )
	{
		return DestinationPtr(new FileDestination(filePath, maxFileSize, maxFileNumber));
	}

	DestinationPtr DestinationFactory::MakeDebugOutputDestination()
	{
		return DestinationPtr(new DebugOutputDestination);
	}

	DestinationPtr DestinationFactory::MakeConsoleDestination()
	{
		return DestinationPtr(new ConsoleDestination);
	}

} // end namespace
